package com.unibeta.cloudtest.mesher.auth.service.impl;

import java.io.File;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.unibeta.cloudtest.config.ConfigurationProxy;
import com.unibeta.cloudtest.mesher.auth.bean.AuthUserDetails;
import com.unibeta.cloudtest.mesher.auth.context.AuthContext;
import com.unibeta.cloudtest.mesher.auth.context.AuthContextHolder;
import com.unibeta.cloudtest.mesher.auth.service.AuthorizationService;
import com.unibeta.cloudtest.mesher.modules.upm.entity.User;
import com.unibeta.cloudtest.mesher.service.UpmService;
import com.unibeta.cloudtest.util.CloudTestUtils;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.signers.JWTSignerUtil;

@Service
public class AuthorizationServiceImpl implements AuthorizationService<User> {

	@Value("${cloudtest.security.aes.key:com.unibeta.cloudtest.editor.aes}")
	private String AES_KEY;

	@Bean
	private AES aes() {
		return SecureUtil
				.aes(SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), AES_KEY.getBytes()).getEncoded());
	}

	private static final int DEFAULT_AUTH_TOKEN_TIMEOUT_SECONDS = TOKEN_EXPIRES_IN_SECONDS_AS_ONE_HOUR;
	@Autowired
	UpmService upmService;
	private static TimedCache<Object, Object> cache = CacheUtil
			.newTimedCache(DEFAULT_AUTH_TOKEN_TIMEOUT_SECONDS * 1000);

	@Override
	public String login(AuthUserDetails<User> authUser) throws Exception {
		Assert.isTrue(authUser != null && authUser.getDetails() != null, "authUser or userDetails is null");
		return this.signToken((AuthUserDetails) upmService.login(authUser.getDetails()));
	}

	@Override
	public AuthUserDetails<User> register(AuthUserDetails<User> authUser) throws Exception {
		Assert.isTrue(authUser != null && authUser.getDetails() != null, "authUser or userDetails is null");
		return (AuthUserDetails) upmService.registerUser(authUser.getDetails());
	}

	@Override
	public boolean changePassword(String username, String oldPassword, String newPassword, String confirmPassword)
			throws Exception {
		return upmService.changePassword(username, oldPassword, newPassword, confirmPassword);
	}

	@Override
	public boolean logout(AuthUserDetails<User> authUser) throws Exception {
		Assert.isTrue(authUser != null && authUser.getDetails() != null, "authUser or userDetails is null");

		cache.remove(authUser.getToken());
		return true;
	}

	@Override
	public String signToken(AuthUserDetails<User> authUser) throws Exception {
		Assert.isTrue(authUser.getUsername() != null && authUser.getPassword() != null,
				"username and password are required");
		Date date = new Date();

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);

		if (AuthUserDetails.ENTITY_TYPE_ONLINE_EDITOR.equalsIgnoreCase(authUser.getEntityType())) {
			calendar.add(Calendar.SECOND, DEFAULT_AUTH_TOKEN_TIMEOUT_SECONDS);
			authUser.setTimeout(DEFAULT_AUTH_TOKEN_TIMEOUT_SECONDS);
		} else {
			Assert.isTrue(false, "not supported operation.");
			
		}

		if (authUser.getDetails() == null || authUser.getDetails().getPassword() == null) {
			var u = new User();
			u.setPassword(MD5.create().digestHex(authUser.getPassword()));
			authUser.setDetails(u);
		}

		String token = JWT.create().setAudience(new String[] { authUser.getUsername() }).setIssuedAt(date)
				.setExpiresAt(calendar.getTime()).setJWTId(authUser.getDetails().getPassword())
				.setSigner(JWTSignerUtil.hs256(authUser.getPassword().getBytes())).sign();

		if (AuthUserDetails.ENTITY_TYPE_ONLINE_EDITOR.equalsIgnoreCase(authUser.getEntityType())) {
			authUser.setIssueDate(date);
			authUser.setExpireDate(calendar.getTime());

			AuthContext context = AuthContextHolder.getContext();
			context.setCurrentUserDetails(authUser);

			Map session = new HashMap();

			context.setCurrentSession(session);
			AuthContextHolder.setContext(context);

			var shortToken = CloudTestUtils.getMD5code(token.getBytes());
			authUser.setToken(shortToken);
			cache.put(shortToken, context);

			return shortToken;
		} else {
			Assert.isTrue(false, "not supported operation.");
			return null;
		}
	}

	@Override
	public boolean authToken(String token) throws Exception {

		boolean authValid = false;
		AuthContext<User> authContext = new AuthContext<>();

		Object cachedToken = cache.get(token);
		if (cachedToken != null && cachedToken instanceof AuthContext) {
			authContext = (AuthContext) cachedToken;

			if (AuthUserDetails.ENTITY_TYPE_ONLINE_EDITOR
					.equalsIgnoreCase(authContext.getCurrentUserDetails().getEntityType())) {
				cache.put(token, authContext, DEFAULT_AUTH_TOKEN_TIMEOUT_SECONDS * 1000);
			} else {
				long exp = Long.valueOf(authContext.getCurrentUserDetails().getExpireDate().getTime()) * 1000L;
				long timeout = exp - new Date().getTime();
				cache.put(token, authContext, timeout);
			}

			authValid = true;
		} else {
			Assert.isTrue(false, "not supported operation.");
		}

		AuthContextHolder.setContext(authContext);
		ConfigurationProxy
				.setCLOUDTEST_HOME(new File(com.unibeta.cloudtest.mesher.config.ConfigurationProxy.getWorkspace() + "/"
						+ AuthContextHolder.getContext().getCurrentUserDetails().getUsername() + "/")
						.getCanonicalPath());

		return authValid;

	}
}
